package demo.rao.mideng.core;

import demo.rao.mideng.annotation.RaoMiDeng;
import demo.rao.mideng.exception.RaoMiDengException;
import demo.rao.mideng.utils.HttpRequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.data.redis.core.StringRedisTemplate;
import com.alibaba.fastjson.JSON;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import redis.clients.jedis.Jedis;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 幂等处理拦截逻辑
 */
@Slf4j
public class MiDengInterceptor implements MethodInterceptor {
    /**
     * 开启前置幂等的话，执行前置幂等逻辑，默认是后置幂等
     * 检索幂等key是否存在，存在的话【说明已经有线程做了处理这个请求】，返回1,不存在返回0
     * 不存在的话，设置幂等key，设置过期时间，删除lock-key
     */
    private static final String before_lua =
            "if redis.call(\"EXISTS\",KEYS[1]) == 1 then\n" +
                    "        return  1\n" +
                    "else\n" +
                    "        redis.call(\"SET\",KEYS[1],ARGV[1])\n" +
                    "        redis.call('EXPIRE',KEYS[1],ARGV[2])\n" +
                    "        redis.call(\"DEL\",KEYS[2])\n" +
                    "        return 0\n" +
                    "end";

    private StringRedisTemplate stringRedisTemplate;

    private static final String pattern = "\"[A-Za-z0-9]+\":";
    /**
     * <? 前置幂等，还是后置幂等
     */
    private boolean before;
    private String expireTime;

    public MiDengInterceptor(StringRedisTemplate stringRedisTemplate, boolean before, String expireTime) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.before = before;
        this.expireTime = expireTime;
    }


    /**
     * 对key压缩，只要属性的value就ok了。简单来说不要存放那么长的key在redis里面去
     */
    private String generateSimpleKey(Object[] arguments) {
        return Arrays.stream(arguments)
                .map(JSON::toJSONString)
                .map(json -> json.replaceAll(pattern, ""))
                .collect(Collectors.joining());
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        HttpServletRequest request = HttpRequestUtil.httpServletRequest();
        //请求的实参
        Object[] arguments = invocation.getArguments();
        String simpleKey = generateSimpleKey(arguments);
        //请求的url
        String url = request.getRequestURI();
        RaoMiDeng annotation = invocation.getMethod().getAnnotation(RaoMiDeng.class);
        //请求前处理幂等操作
        String redisKey = simpleKey + url;

        /**<? 超时重试肯定不是同一个线程去处理，所以就会有多线程问题，但这里一定不会高并发，顶多就是重试次数的线程数，间隔重试的*/
//        if (before) {
//            return handlerBefore(invocation, annotation, redisKey);
//        } else {//请求处理完成后进行幂等操作
//            return handlerAfter(invocation, redisKey);
//        }
        return handlerAfter(invocation, redisKey);
    }

    private Object handlerAfter(MethodInvocation invocation, String redisKey) throws Throwable {
        Object proceed = invocation.proceed();
        if (stringRedisTemplate.opsForValue().setIfAbsent(redisKey, ""/*JSON.toJSONString(proceed)*/, 30, TimeUnit.SECONDS)) {
            return proceed;
        } else {
            //设置为事务为回滚状态
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return proceed;
            //说明有线程处理succeed，setnx失败的线程就要回滚。
//                throw new RaoMiDengException(annotation.message());
        }
    }

    /**
     * todo 这个逻辑错了，后续在完成
     */
    @Deprecated
    private Object handlerBefore(MethodInvocation invocation, RaoMiDeng annotation, String redisKey) throws Throwable {
        String lockKey = redisKey + "lock";
        //加锁成功的往下走
//            while (!stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "", annotation.time(), TimeUnit.SECONDS)) {
//                Thread.sleep(10);
//            }
        //失败了直接不处理，这里要知道超时重试的间隔时间就好了，@value，不过一般超时重试好像是不会有多线程的问题
        if (!stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "", annotation.time(), TimeUnit.SECONDS)) {
            throw new RaoMiDengException(annotation.message());
        }
        Object proceed = invocation.proceed();
        //keys
        List<String> keys = new ArrayList<>();
        keys.add(redisKey);
        keys.add(lockKey);
        //argv
        List<String> params = new ArrayList<>();
//            params.add(JSON.toJSONString(proceed));
        params.add("1");
        //幂等key-30s过期
        params.add(expireTime);
        Object eval;
        boolean flag = true;
        try {
            Jedis nativeConnection = (Jedis) stringRedisTemplate.getConnectionFactory().getConnection().getNativeConnection();
            /**<? 拿到不为null的值，就可以抛异常回滚*/
            eval = nativeConnection.eval(before_lua, keys, params);
            flag = false;
        } finally {
            //虽然lua脚本有删除lock-key的，如果lua脚本执行正常的话，就无需再次del-lock-key
            if (flag) {
                stringRedisTemplate.delete(lockKey);
            }
        }
        //1代表之前有人处理了，报异常，让事务回滚
        if ("1".equals(eval)) {
            throw new RaoMiDengException(annotation.message());
        }
        return proceed;
    }
}
